﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleActor.Test
{
    [Flags]
    public enum TestMode
    {
        None = 0x00,
        ThrowOnStart = 0x01,
        ThrowOnStop  = 0x02,
    };

    public interface ITest
    {
        Task Action0();
        Task Action1(byte param1);
        Task Action2(byte param1, short param2);
        Task Action3(byte param1, short param2, int param3);
        Task Action4(byte param1, short param2, int param3, long param4);

        Task Action0ThrowsException();

        Task LongAction0ThrowsException(TimeSpan delay);
        Task LongAction1(byte param1, TimeSpan delay);
        Task LongAction2(byte param1, short param2, TimeSpan delay);

        Task AsyncAction1(byte param1, TimeSpan delay);

        Task<string> Func0();
        Task<string> Func1(long param1);
        Task<string> Func2(long param1, int param2);
        Task<string> Func3(long param1, int param2, short param3);
        Task<string> Func4(long param1, int param2, short param3, byte param4);

        Task<string> Func0ThrowsException();

        Task<object> GetParameter();

        IObservable<int> Prop { get; }
    }

    public interface ITestService : ITest, IActor
    {
    }

    internal interface ITestServant : ITest, IActorServant
    {
    }

    public static class TestServiceFactory
    {
        public static ITestService Create()
        {
            var servant = new TestServant();
            var scheduler = new MessageScheduler<ITestServant>("Service", servant);
            var proxy = new TestServiceProxy(scheduler);
            return proxy;
        }

        public static ITestService Create(int boundedCapacity)
        {
            var servant = new TestServant();
            var scheduler = new MessageScheduler<ITestServant>("Service", servant, boundedCapacity);
            var proxy = new TestServiceProxy(scheduler);
            return proxy;
        }
    }

    internal class TestServiceProxy : ActorProxyBase<ITestServant>, ITestService
    {
        public TestServiceProxy(IMessageScheduler<ITestServant> scheduler)
            : base(scheduler)
        {
        }

        public Task Action0()
        {
            var func = Scheduler.GetQueueAction(s => s.Action0());
            var ret = func();
            return ret;
        }

        public Task Action1(byte param1)
        {
            var func = Scheduler.GetQueueAction<byte>((s, p1) => s.Action1(p1));
            var ret = func(param1);
            return ret;
        }

        public Task Action2(byte param1, short param2)
        {
            var func = Scheduler.GetQueueAction<byte, short>((s, p1, p2) => s.Action2(p1, p2));
            var ret = func(param1, param2);
            return ret;
        }

        public Task Action3(byte param1, short param2, int param3)
        {
            var func = Scheduler.GetQueueAction<byte, short, int>((s, p1, p2, p3) => s.Action3(p1, p2, p3));
            var ret = func(param1, param2, param3);
            return ret;
        }

        public Task Action4(byte param1, short param2, int param3, long param4)
        {
            var func = Scheduler.GetQueueAction<byte, short, int, long>((s, p1, p2, p3, p4) => s.Action4(p1, p2, p3, p4));
            var ret = func(param1, param2, param3, param4);
            return ret;
        }

        public Task Action0ThrowsException()
        {
            var func = Scheduler.GetQueueAction(s => s.Action0ThrowsException());
            var ret = func();
            return ret;
        }

        public Task LongAction0ThrowsException(TimeSpan delay)
        {
            var func = Scheduler.GetQueueAction<TimeSpan>((s, p1) => s.LongAction0ThrowsException(p1));
            var ret = func(delay);
            return ret;
        }

        public Task LongAction1(byte param1, TimeSpan delay)
        {
            var func = Scheduler.GetQueueAction<byte, TimeSpan>((s, p1, p2) => s.LongAction1(p1, p2));
            var ret = func(param1, delay);
            return ret;
        }

        public Task LongAction2(byte param1, short param2, TimeSpan delay)
        {
            var func = Scheduler.GetQueueAction<byte, short, TimeSpan>((s, p1, p2, p3) => s.LongAction2(p1, p2, p3));
            var ret = func(param1, param2, delay);
            return ret;
        }

        public Task AsyncAction1(byte param1, TimeSpan delay)
        {
            var func = Scheduler.GetQueueAction<byte, TimeSpan>((s, p1, p2) => s.AsyncAction1(p1, p2));
            var ret = func(param1, delay);
            return ret;
        }

        public Task<string> Func0()
        {
            var func = Scheduler.GetQueueFunc<string>(s => s.Func0());
            var ret = func();
            return ret;
        }

        public Task<string> Func1(long param1)
        {
            var func = Scheduler.GetQueueFunc<long, string>((s, p1) => s.Func1(p1));
            var ret = func(param1);
            return ret;
        }

        public Task<string> Func2(long param1, int param2)
        {
            var func = Scheduler.GetQueueFunc<long, int, string>((s, p1, p2) => s.Func2(p1, p2));
            var ret = func(param1, param2);
            return ret;
        }

        public Task<string> Func3(long param1, int param2, short param3)
        {
            var func = Scheduler.GetQueueFunc<long, int, short, string>((s, p1, p2, p3) => s.Func3(p1, p2, p3));
            var ret = func(param1, param2, param3);
            return ret;
        }

        public Task<string> Func4(long param1, int param2, short param3, byte param4)
        {
            var func = Scheduler.GetQueueFunc<long, int, short, byte, string>((s, p1, p2, p3, p4) => s.Func4(p1, p2, p3, p4));
            var ret = func(param1, param2, param3, param4);
            return ret;
        }

        public Task<string> Func0ThrowsException()
        {
            var func = Scheduler.GetQueueFunc<string>(s => s.Func0ThrowsException());
            var ret = func();
            return ret;
        }

        public Task<object> GetParameter()
        {
            var func = Scheduler.GetQueueFunc<object>(s => s.GetParameter());
            var ret = func();
            return ret;
        }

        public IObservable<int> Prop
        {
            get
            {
                var ret = Scheduler.GetObservable(s => s.Prop);
                return ret;
            }
        }
    }

    internal class TestServant : ActorServantBase, ITestServant
    {
        private readonly TestMode mode;
        private readonly Subject<int> prop;
        private object parameter;

        public TestServant()
            : this(TestMode.None)
        {
        }

        public TestServant(TestMode mode)
        {
            this.mode = mode;
            this.prop = new Subject<int>();
            this.parameter = null;
        }

        public override void OnStart()
        {
            base.OnStart();
            if (mode.HasFlag(TestMode.ThrowOnStart))
            {
                throw new SampleException("OnStart");
            }
        }

        public override void OnStop()
        {
            base.OnStop();
            if (mode.HasFlag(TestMode.ThrowOnStop))
            {
                throw new SampleException("OnStop");
            }
        }

        public override void Run()
        {
            while (Handler.TryHandleMessage(TimeSpan.FromMilliseconds(1)).CanNext())
            {
            }
        }

        private string UpdateParameter(string methodName, object param)
        {
            parameter = param;
            var ret = methodName;

            Debug.WriteLine("{0:yyyy/MM/dd HH:mm:ss.fff} TestServant.UpdateParameter: {1}({2})", DateTime.Now, methodName, param);
            return ret;
        }

        public Task Action0()
        {
            UpdateParameter("Action0", Unit.Default);

            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public Task Action1(byte param1)
        {
            UpdateParameter("Action1", Tuple.Create(param1));
            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public Task Action2(byte param1, short param2)
        {
            UpdateParameter("Action2", Tuple.Create(param1, param2));
            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public Task Action3(byte param1, short param2, int param3)
        {
            UpdateParameter("Action3", Tuple.Create(param1, param2, param3));
            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public Task Action4(byte param1, short param2, int param3, long param4)
        {
            UpdateParameter("Action4", Tuple.Create(param1, param2, param3, param4));
            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public Task Action0ThrowsException()
        {
            Debug.WriteLine("{0:yyyy/MM/dd HH:mm:ss.fff} TestServant.Action0ThrowsException", DateTime.Now);
            throw new SampleException("Action0ThrowsException");
        }

        public Task LongAction0ThrowsException(TimeSpan delay)
        {
            Thread.Sleep(delay);

            Debug.WriteLine("{0:yyyy/MM/dd HH:mm:ss.fff} TestServant.LongAction0ThrowsException", DateTime.Now);
            throw new SampleException("LongAction0ThrowsException");
        }

        public Task LongAction1(byte param1, TimeSpan delay)
        {
            Thread.Sleep(delay);

            UpdateParameter("LongAction1", Tuple.Create(param1));
            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public Task LongAction2(byte param1, short param2, TimeSpan delay)
        {
            Thread.Sleep(delay);

            UpdateParameter("LongAction2", Tuple.Create(param1, param2));
            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public async Task AsyncAction1(byte param1, TimeSpan delay)
        {
            await Task.Delay(delay);

            UpdateParameter("AsyncAction1", Tuple.Create(param1));
        }

        public Task<string> Func0()
        {
            var result = UpdateParameter("Func0", Unit.Default);
            var ret = Task.FromResult(result);
            return ret;
        }

        public Task<string> Func1(long param1)
        {
            var result = UpdateParameter("Func1", Tuple.Create(param1));
            var ret = Task.FromResult(result);
            return ret;
        }

        public Task<string> Func2(long param1, int param2)
        {
            var result = UpdateParameter("Func2", Tuple.Create(param1, param2));
            var ret = Task.FromResult(result);
            return ret;
        }

        public Task<string> Func3(long param1, int param2, short param3)
        {
            var result = UpdateParameter("Func3", Tuple.Create(param1, param2, param3));
            var ret = Task.FromResult(result);
            return ret;
        }

        public Task<string> Func4(long param1, int param2, short param3, byte param4)
        {
            var result = UpdateParameter("Func4", Tuple.Create(param1, param2, param3, param4));
            var ret = Task.FromResult(result);
            return ret;
        }

        public Task<string> Func0ThrowsException()
        {
            Debug.WriteLine("{0:yyyy/MM/dd HH:mm:ss.fff} TestServant.Func0ThrowsException", DateTime.Now);
            throw new SampleException("Func0");
        }

        public Task<object> GetParameter()
        {
            var ret = Task.FromResult(parameter);
            return ret;
        }

        public IObservable<int> Prop
        {
            get { return prop; }
        }
    }
}
